package server;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Iterator;

import communication.AbandonConversationRequest;
import communication.AcceptFileTransferRequest;
import communication.AcceptNewBuddyRequest;
import communication.AddNewBuddyRequest;
import communication.AddNewGroupRequest;
import communication.BuddyPreferencesRequest;
import communication.BuddyStatusChangedRequest;
import communication.ConfirmFileTransferRequest;
import communication.ConfirmNewBuddyRequest;
import communication.ConversationOpenedRequest;
import communication.ErrorRequest;
import communication.FileAcceptedRequest;
import communication.InitFileTransferRequest;
import communication.InviteBuddyChatRoomRequest;
import communication.NewAccountRequest;
import communication.OpenConversationRequest;
import communication.ParticipantJoinedRequest;
import communication.ParticipantLeftRequest;
import communication.ReceiveMessageRequest;
import communication.RejectNewBuddyRequest;
import communication.RemoveBuddyFromGroupRequest;
import communication.RemoveGroupRequest;
import communication.Request;
import communication.SendMessageRequest;
import communication.SignOffRequest;
import communication.SignOnRequest;
import communication.StillOnlineRequest;
import communication.StoreNewPreferencesRequest;

/**
 * <h1>Class RequestConsumer</h1>
 * 
 * This class represents a thread which is serving the requests made by the
 * users.
 * 
 * @author Tomas
 * @date March 4 2009
 */
public class RequestConsumer extends Thread{

	/**
	 * Server instance.
	 */
	protected ServerMonitor server = null;
	
	/**
	 * Boolean value to indicate if the thread is running. If we want to
	 * turn of the server, we should do it nicely.
	 */
	protected boolean finished;
	
	/**
	 * Statistics.
	 */
	protected Statistics stats = null;
	
	/**
	 * Database.
	 */
	protected DBManager database = null;
	
	/**
	 * Server name.
	 */
	public static final String SERVER_NAME = "parlanchin_server_superuser";
	
	/**
	 * Default constructor. Initializes a thread to consume and serve Requests.
	 */
	public RequestConsumer(){
		
		finished = false;
		
	}
	
	/**
	 * Main loop for the thread. Reads requests and parse them.
	 */
	public void run(){
		
		server = ServerMonitor.getInstance();
		database = DBManager.getInstance();
		stats = Statistics.getInstance();
		
		while(!finished){
			Request req = server.getRequest();
			
			if(req == null){
				try{
					Thread.sleep(200);
				}catch(InterruptedException ie){
					LogException.getInstance().logException(ie);
				}
			}else{
				if(req instanceof SendMessageRequest){
					
					processSendMessageRequest((SendMessageRequest) req);
					
				}else if(req instanceof StillOnlineRequest){
					
					processStillOnlineRequest((StillOnlineRequest) req);
					
				}else if(req instanceof OpenConversationRequest){
					
					processOpenConversationRequest((OpenConversationRequest) req);
					
				}else if(req instanceof AbandonConversationRequest){
					
					processAbandonConversationRequest((AbandonConversationRequest) req);
					
				}else if(req instanceof InviteBuddyChatRoomRequest){
					
					processInviteBuddyChatRoomRequest((InviteBuddyChatRoomRequest) req);
					
				}else if(req instanceof SignOnRequest){
					
					processSignOnRequest((SignOnRequest) req);
					
				}else if(req instanceof SignOffRequest){
					
					processSignOffRequest((SignOffRequest) req);
					
				}else if(req instanceof BuddyStatusChangedRequest){
					
					processBuddyStatusChangedRequest((BuddyStatusChangedRequest) req);
					
				}else if(req instanceof AddNewBuddyRequest){
					
					processAddNewBuddyRequest((AddNewBuddyRequest) req);
					
				}else if(req instanceof AddNewGroupRequest){
					
					processAddNewGroupRequest((AddNewGroupRequest) req);
					
				}else if(req instanceof AcceptNewBuddyRequest){
					
					processAcceptNewBuddyRequest((AcceptNewBuddyRequest) req);
					
				}else if(req instanceof RejectNewBuddyRequest){
					
					processRejectNewBuddyRequest((RejectNewBuddyRequest) req);
					
				}else if(req instanceof RemoveBuddyFromGroupRequest){
					
					processRemoveBuddyFromGroupRequest((RemoveBuddyFromGroupRequest) req);
					
				}else if(req instanceof RemoveGroupRequest){
					
					processRemoveGroupRequest((RemoveGroupRequest) req);
					
				}else if(req instanceof StoreNewPreferencesRequest){
					
					processStoreNewPreferencesRequest((StoreNewPreferencesRequest) req);
					
				}else if(req instanceof InitFileTransferRequest){
					
					processInitFileTransferRequest((InitFileTransferRequest) req);
					
				}else if(req instanceof AcceptFileTransferRequest){
					
					processAcceptFileTransferRequest((AcceptFileTransferRequest) req);
					
				}else if(req instanceof NewAccountRequest){
					
					processNewAccountRequest((NewAccountRequest) req);
					
				}
			}
		}
		
	}

	/**
	 * Finishes the thread.
	 */
	public void finish(){
		
		finished = true;
		stats = Statistics.getInstance();
		
	}
	
	private Session getSenderSession(Request req){
		
		String sender = req.getSender();
		Session s = server.getSession(sender);
		if(s != null){
			s.updateActivity();
		}
		
		return s;
		
	}
	
	private void processSendMessageRequest(SendMessageRequest smr){
		
		Session session = getSenderSession(smr);
		
		String sender = smr.getSender();
		String convID = smr.getReceiver();
		String message = smr.getMessage();
		
		
		System.out.println("Message received: " + convID + " " + message);
		
		if(convID.startsWith("offline")){
			String receiver = convID.substring(7);
			database.addOfflineMessage(smr.getSender(), receiver, message);
			
			session.sendRequest(new ReceiveMessageRequest(session.getOwner().getUsername(), convID, message, convID));
		}else{
			Conversation conv = server.getConversation(convID);
			
			if(conv != null){
				Iterator<Session> it = conv.getParticipants();
				ReceiveMessageRequest rmr = new ReceiveMessageRequest(sender, convID, message, convID);
				
				while(it.hasNext()){
					Session rec = it.next();
					rec.sendRequest(rmr);
				}
			}
		}
		
		stats.addMessageLength(message.length());
		
	}
	
	private void processSignOnRequest(SignOnRequest sor){
		
		String username = sor.getSender();
		String password = sor.getPassword();
		InetAddress IP = sor.getIP();
		int port = sor.getPort();
		
		Person p = database.authenticate(username, password);
		if(p != null){
			BuddyList bl = database.getBuddyList(username);
			Preferences pref = database.getPreferences(username);
			Session newSession = new Session(new Person(username), IP, port, bl);
			server.addSession(newSession);
			
			System.out.println("User " + username + " successfully logged in.");
			
			Iterator<Group> itgroup = bl.getGroups();
			while(itgroup.hasNext()){
				Group g = itgroup.next();
				Iterator<Person> it = g.getMembers();
				while(it.hasNext()){
					Person member = it.next();
					if(server.getSession(member.getUsername()) != null){
						member.setStatus(Status.ONLINE);
					}
				}
			}
			
			newSession.sendRequest(new BuddyPreferencesRequest(SERVER_NAME, p, bl, pref));
			sendStatusChanged(newSession, Status.ONLINE);
			
			// Send offline messages
			Iterator<String> offlineAuthors = database.getOfflineMessageAuthors(username);
			ArrayList<String> participants = new ArrayList<String>();
			participants.add(username);
			while(offlineAuthors.hasNext()){
				String author = offlineAuthors.next();
				String convID = "offline" + author;
				newSession.sendRequest(new ConversationOpenedRequest(SERVER_NAME, participants, convID));
				
				newSession.sendRequest(new ReceiveMessageRequest(SERVER_NAME, convID, "Offline messages received\n", convID));
				
				Iterator<String> offlineMessages = database.getOfflineMessages(username, author);
				while(offlineMessages.hasNext()){
					String mssg = offlineMessages.next();
					
					newSession.sendRequest(new ReceiveMessageRequest(author, convID, mssg, convID));
				}
			}
			database.removeOfflineMessages(username);
			
			// Send pending requests
			Iterator<String> pendingMembers = database.pendingRequests(username);
			while(pendingMembers.hasNext()){
				String name = pendingMembers.next();
				newSession.sendRequest(new ConfirmNewBuddyRequest(name));
			}
			
		}else{
			Session errorSession = new Session(null, IP, port, null);
			errorSession.sendRequest(new ErrorRequest(SERVER_NAME, "Incorrect username or password"));
		}
		
	}
	
	private void processSignOffRequest(SignOffRequest sor){
		
		Session session = getSenderSession(sor);
		if(session != null){
			server.removeSession(session.getOwner().getUsername());
		}
		
		sendStatusChanged(session, Status.OFFLINE);
	}
	
	private void sendStatusChanged(Session session, Status status){
		
		if(session != null){
			BuddyList bl = session.getBuddyList();
			Iterator<Group> itgroup = bl.getGroups();
			
			BuddyStatusChangedRequest bscr = new BuddyStatusChangedRequest(SERVER_NAME, session.getOwner().getUsername(), status);
			
			while(itgroup.hasNext()){
				Group g = itgroup.next();
				Iterator<Person> it = g.getMembers();
				
				while(it.hasNext()){
					Person p = it.next();
					Session s = server.getSession(p.getUsername());
					
					if(s != null){
						s.sendRequest(bscr);
					}
				}
			}
		}
		
	}

	private void processStoreNewPreferencesRequest(StoreNewPreferencesRequest req) {
		
		String sender = req.getSender();
		Preferences pref = req.getPreferences();
		
		database.storePreferences(sender, pref);
		
	}

	private void processRejectNewBuddyRequest(RejectNewBuddyRequest req) {
		
		String sender = req.getSender();
		String receiver = req.getReceiver();
		
		database.rejectFriend(sender, receiver);
		
	}

	private void processAcceptNewBuddyRequest(AcceptNewBuddyRequest req) {
		
		String sender = req.getSender();
		String senderGroup = req.getGroup();
		String buddy = req.getReceiver();
		String buddyGroup = database.getPendingGroup(buddy, sender);
		
		database.confirmBuddyToGroup(sender, buddy, senderGroup);
		database.confirmBuddyToGroup(buddy, sender, buddyGroup);
		database.deletePending(buddy, sender, buddyGroup);
		
		Session senderSession = server.getSession(sender);
		Session buddySession = server.getSession(buddy);
		
		if(senderSession != null){
			BuddyList bl = senderSession.getBuddyList();
			Group g = bl.findGroup(senderGroup);
			updateBuddyStatus(bl);
			if(g != null){
				Person p = new Person(buddy);
				if(buddySession != null){
					p.setStatus(Status.ONLINE);
				}
				g.addMember(p);
				senderSession.sendRequest(new BuddyPreferencesRequest(SERVER_NAME, null, bl, null));
			}
		}
		
		if(buddySession != null){
			BuddyList bl = buddySession.getBuddyList();
			Group g = bl.findGroup(buddyGroup);
			updateBuddyStatus(bl);
			if(g != null){
				Person p = new Person(sender);
				if(senderSession != null){
					p.setStatus(Status.ONLINE);
				}
				g.addMember(p);
				buddySession.sendRequest(new BuddyPreferencesRequest(SERVER_NAME, null, bl, null));
			}
		}
	}

	private void updateBuddyStatus(BuddyList bl) {
		
		Iterator<Group> itgroup = bl.getGroups();
		
		while(itgroup.hasNext()){
			Group g = itgroup.next();
			Iterator<Person> it = g.getMembers();
			
			while(it.hasNext()){
				Person p = it.next();
				
				if(server.getSession(p.getUsername()) != null){
					p.setStatus(Status.ONLINE);
				}else{
					p.setStatus(Status.OFFLINE);
				}
			}
		}
		
	}

	private void processAddNewGroupRequest(AddNewGroupRequest req) {
		
		Session s = getSenderSession(req);
		String groupName = req.getGroupName();
		
		if(database.createGroup(s.getOwner().getUsername(), groupName)){
			Group g = new Group(groupName);
			s.getBuddyList().addGroup(g);
		}
		
	}

	private void processAddNewBuddyRequest(AddNewBuddyRequest req) {
		
		String sender = req.getSender();
		String buddy = req.getBuddyName();
		String group = req.getGroupName();
		
		boolean result = database.addBuddyToGroup(sender, buddy, group);
		
		Session senderSession = getSenderSession(req);
		BuddyList bl = senderSession.getBuddyList();
		Group g = bl.findGroup(group);
		if(g != null){
			g.addMember(new Person(buddy));
		}
		
		Session s = server.getSession(buddy);
		if(s != null && !result){
			ConfirmNewBuddyRequest cnbr = new ConfirmNewBuddyRequest(sender);
			s.sendRequest(cnbr);
		}
		
	}
	
	private void processRemoveBuddyFromGroupRequest(RemoveBuddyFromGroupRequest req) {
		
		String sender = req.getSender();
		String group = req.getGroupName();
		String buddy = req.getBuddyName();
		Session s = getSenderSession(req);
		
		if(database.deleteBuddyFromGroup(sender, buddy, group)){
			if(s != null){
				Group g = s.getBuddyList().findGroup(group);
				if(g != null){
					Person p = g.findPerson(buddy);
					g.removeMember(p);
				}
			}
		}
		
	}
	
	private void processRemoveGroupRequest(RemoveGroupRequest req){
		
		String sender = req.getSender();
		String group = req.getGroupName();
		Session s = getSenderSession(req);
		
		if(database.deleteGroup(sender, group)){
			if(s != null){
				Group g = new Group(group);
				s.getBuddyList().addGroup(g);
			}
		}
		
	}
	
	private void processNewAccountRequest(NewAccountRequest nar){
		
		String username = nar.getSender();
		String password = nar.getPassword();
		InetAddress IP = nar.getIP();
		int port = nar.getPort();
		
		if(database.createAccount(username, password)){
			Session s = new Session(null, IP, port, null);
			s.sendRequest(new ErrorRequest(SERVER_NAME, "Your account was created successfully!"));
			Preferences pref = new Preferences();
			database.storePreferences(username, pref);
		}else{
			Session s = new Session(null, IP, port, null);
			s.sendRequest(new ErrorRequest(SERVER_NAME, "The username already exists. Please, choose another username."));
		}
		
	}

	private void processStillOnlineRequest(StillOnlineRequest sor){
		
		Session s = getSenderSession(sor);
		s.updateActivity();
		
	}
	
	private void processInviteBuddyChatRoomRequest(InviteBuddyChatRoomRequest ibcrr) {
		
		String convID = ibcrr.getConvID();
		String buddy = ibcrr.getBuddy();
		
		Conversation conv = server.getConversation(convID);
		Session newParticipant = server.getSession(buddy);
		
		Iterator<Session> it = conv.getParticipants();
		ArrayList<String> participants = new ArrayList<String>();
		
		ParticipantJoinedRequest pjr = new ParticipantJoinedRequest(SERVER_NAME, ibcrr.getBuddy(), ibcrr.getConvID());
		
		// Notify all the participants about the new join
		while(it.hasNext()){
			Session part = it.next();
			participants.add(part.getOwner().getUsername());
			part.sendRequest(pjr);
		}
		
		// Notify the new participant
		participants.add(ibcrr.getBuddy());
		conv.addParticipant(newParticipant);
		ConversationOpenedRequest cor = new ConversationOpenedRequest(SERVER_NAME, participants, ibcrr.getConvID());
		newParticipant.sendRequest(cor);
		
	}

	private void processAbandonConversationRequest(AbandonConversationRequest acr) {
		
		Session sender = getSenderSession(acr);
		
		if(sender != null){
			synchronized(sender){
				String convID = acr.getID();
				Conversation conv = server.getConversation(convID);
				if(conv != null){
					conv.removeParticipant(sender);
					
					ParticipantLeftRequest plr = new ParticipantLeftRequest(SERVER_NAME, acr.getSender(), acr.getID());
					Iterator<Session> it = conv.getParticipants();
					
					if(it.hasNext()){
						while(it.hasNext()){
							Session part = it.next();
							part.sendRequest(plr);
						}
					}else{
						server.removeConversation(convID);
					}
				}
			}
		}
		
	}

	private void processOpenConversationRequest(OpenConversationRequest ocr) {
		
		Session senderSession = getSenderSession(ocr);
		
		if(senderSession != null){
			String talkBuddy = ocr.getTalkBuddy();
			Session buddySession = server.getSession(talkBuddy);
			if(buddySession != null){
				Conversation conv = new Conversation(senderSession);
				conv.addParticipant(buddySession);
				
				server.addConversation(conv);
				
				ArrayList<String> participants = new ArrayList<String>();
				participants.add(senderSession.getOwner().getUsername());
				participants.add(talkBuddy);
				
				ConversationOpenedRequest cor = new ConversationOpenedRequest(SERVER_NAME, participants, conv.getID());
				senderSession.sendRequest(cor);
				buddySession.sendRequest(cor);
			}else{
				// 
			}
			
		}
		
	}

	private void processBuddyStatusChangedRequest(BuddyStatusChangedRequest bscr) {
		
		Session s = getSenderSession(bscr);
		
		if(s != null){
			sendStatusChanged(s, bscr.getStatus());
		}
		
	}
	
	private void processInitFileTransferRequest(InitFileTransferRequest iftr){
		
		Session senderSession = getSenderSession(iftr);
		
		if(senderSession != null){
			String sender = iftr.getSender();
			String receiver = iftr.getReceiver();
			String path = iftr.getFilePath();
			String file = iftr.getFileName();
			InetAddress senderip = iftr.getIP();
			int port = iftr.getPort();
			
			Session receiverSession = server.getSession(receiver);
			
			if(receiverSession != null){
				ConfirmFileTransferRequest cftr = new ConfirmFileTransferRequest(sender, receiver, path, file, senderip, port);
				receiverSession.sendRequest(cftr);
			}
		}
		
	}
	
	private void processAcceptFileTransferRequest(AcceptFileTransferRequest aftr){
		
		String sender = aftr.getSender();
		String originator = aftr.getOriginator();
		String filename = aftr.getFileName();
		InetAddress ip = aftr.getIP();
		int port = aftr.getPort();
		
		System.out.println("Accepting file from: " + originator);
		System.out.println("File: " + filename);
		System.out.println("IP: " + ip + ":" + port);
		
		FileAcceptedRequest far = new FileAcceptedRequest(sender, originator, filename, filename, ip, port);
		
		Session origSession = server.getSession(originator);
		
		if(origSession != null){
			System.out.println("Sending confirmation");
			origSession.sendRequest(far);
		}
		
	}
}
